use super::{
    buf::Buf, err, err_value, json, ok, selbox::SelBox, throw_err, Base, Client, ECode, MapSv,
    RNull, Resp, TBase, VMap, Value,
};
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use tracing::{instrument, warn};

pub struct Task {
    line: String,
    sel_sn: String,
    buf: Buf,
}

impl Base for Task {}
impl TBase for Task {}

impl Task {
    pub fn new(line: &str, sel_sn: &str) -> Self {
        Self {
            line: line.to_string(),
            sel_sn: sel_sn.to_string(),
            buf: Buf::new(line, sel_sn, Some("task")),
        }
    }

    pub async fn put(&mut self, data: &MapSv, cfg: &MapSv, client: &Client) -> RNull {
        let in_to = cfg.ck_str("inner_e")?;
        let out_to = cfg.ck_str("outer_e")?;

        let inval = json!({
            "tp": "inner",
            "box": data.ck_str("boxin")?,
            "sn": data.ck_str("insn")?,
            "from": data.ck_str("posin")?,
            "to": in_to,
        });

        let outval = json!({
            "tp": "outer",
            "box": data.ck_str("boxout")?,
            "sn": data.ck_str("sn")?,
            "from": data.ck_str("posout")?,
            "to": out_to,
        });

        self.buf.put("inner", inval, client).await?;
        self.buf.put("outer", outval, client).await?;

        Ok(())
    }

    pub async fn ck(
        &mut self,
        tp: &str,
        data: &MapSv,
        buf: &Buf,
        cfg: &MapSv,
        client: &Client,
    ) -> Resp {
        let inner = data.ck_bool(tp)?;
        let inpos = data.ck_bool(&format!("{tp}_s"))?;
        let outpos = data.ck_bool(&format!("{tp}_e"))?;

        if !inner {
            throw_err!(ECode::EmptyData, "no data");
        }

        let selbox = SelBox::new(&self.line, &self.sel_sn);

        if inpos {
            let from = cfg.ck_str(&format!("{tp}_s"))?;
            let mut val = buf.pop(tp, client).await?;

            val.insert("from".to_string(), json!(from));
            val.insert("tp".to_string(), json!(tp));

            selbox.set(&val, client).await?;

            self.addr(&val)
        } else if !outpos {
            let val = self.buf.pop(tp, client).await?;

            selbox.rm(&val, client).await?;

            self.addr(&val)
        } else {
            err!(ECode::EmptyData, "no data")
        }
    }

    #[instrument(skip_all, name = "addr")]
    fn addr(&self, data: &MapSv) -> Resp {
        let sn = data.ck_str("sn")?;
        let from = data.ck_str("from")?;
        let to = data.ck_str("to")?;

        let cmd = json!({
            "sn": sn,
            "from": from,
            "to": to,
        });

        ok!(cmd)
    }

    #[instrument(skip_all, name = "send_plc")]
    async fn send_plc(&self, data: &MapSv, cfg: &MapSv, client: &Client) -> RNull {
        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;
        let from = data.ck_str("from")?;
        let to = data.ck_str("to")?;
        let ip = cfg.ck_str("ip")?;

        warn!("send plc: get config");

        let mut head = HeaderMap::new();
        head.insert(CONTENT_TYPE, self.to_head("application/json")?);

        let cmd = json!({
            tp: {
                "sn": sn,
                "from": from,
                "to": to,
            }
        });

        let data = cmd.to_string();

        client.send_http(&data, ip, 30, head).await?;

        Ok(())
    }

    fn to_head(&self, val: &str) -> Result<HeaderValue, Value> {
        HeaderValue::from_str(val).or_else(|e| err!(ECode::Post, e))
    }
}
